////////////////////////////////////////////////////////////////////////////////////////////////////
//
//	Crytek Character Animation source code
//	
//	History:
//	20/3/2005 - Created by Ivo Herzeg <ivo@crytek.de>
//
//  Contains:
//  loads and initialises CGA objects 
/////////////////////////////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include <CryHeaders.h>
#include <StackContainer.h>

#include "Model.h"
#include "LoaderCGA.h"
#include "LoaderTCB.h"
#include "AnimEventLoader.h"


#define ANIMATION_EXT "anm"
#define CONT_EXTENSION (0x10)

#define NUM_MAX_BONES_PER_GROUP (50)


//////////////////////////////////////////////////////////////////////////
// Loads animation object.
//////////////////////////////////////////////////////////////////////////
CCharacterModel* CryCGALoader::LoadNewCGA( const char* OriginalGeomName, CharacterManager* pManager, uint32 nLoadingFlags )
{
	CRY_DEFINE_ASSET_SCOPE( "CGA",OriginalGeomName );

	Reset();
	//return 0;

	uint32 c=0;
	char geomName[256];
	for (c=0; c<256; c++)
	{
		geomName[c] = OriginalGeomName[c];
		if (geomName[c]==0)
			break;
	}

	if (c > 7) if (geomName[c-8]=='_' && geomName[c-7]=='l' && geomName[c-6]=='o' && geomName[c-5]=='w')
	{
		geomName[c-8]='.'; 
		geomName[c-7]='c'; 
		geomName[c-6]='g'; 
		geomName[c-5]='a';
		geomName[c-4]=0;
	}

	CLoaderTCB loader;

	loader.SetLoadOldChunks(Console::GetInst().ca_LoadUncompressedChunks > 0);
	CHeaderTCB * pSkinningInfo = loader.LoadTCB(OriginalGeomName, 0) ;

	if (pSkinningInfo==0)
	{
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING,	VALIDATOR_FLAG_FILE,OriginalGeomName,	"Failed to load CGA-Object" );
		return 0;
	}

	//CSkinningInfo* pSkinningInfo = pCGF->GetSkinningInfo();
	//if (pSkinningInfo==0) 
	//	return 0;

	CCharacterModel* pCGAModel = new CCharacterModel(OriginalGeomName, pManager, CGA);

	//we use the file-path to calculate a unique codeID for every controller
	uint32 nControllerJID=0x1000;  //g_pCrc32Gen->GetCRC32(geomName);
	
	//-----------------------------------------------------------------------------
	//-----------------------------------------------------------------------------
	//-----------------------------------------------------------------------------

	m_DefaultNodeCount=0;	
	InitNodes(pSkinningInfo,pCGAModel,OriginalGeomName,"Default",1, nControllerJID, nLoadingFlags );
	m_DefaultNodeCount = m_arrNodeAnims.size();
//	g_pI3DEngine->ReleaseChunkFileContent( pCGF );

	LoadAnimations( geomName,pCGAModel,nControllerJID, nLoadingFlags );

	pCGAModel->SetModelAnimEventDatabase(PathUtil::ReplaceExtension(geomName, "animevents"));
	if (gEnv->pCryPak->IsFileExist(pCGAModel->GetModelAnimEventDatabaseCStr()))
		AnimEventLoader::loadAnimationEventDatabase(pCGAModel, pCGAModel->GetModelAnimEventDatabaseCStr());

	// the first step is for the root bone
	uint32 numJoints = uint32(pCGAModel->m_ModelSkeleton.m_arrModelJoints.size());
	for (uint32 i=0; i<numJoints; i++)
	{
		const char* pJointName = pCGAModel->m_ModelSkeleton.m_arrModelJoints[i].GetJointName();
		pCGAModel->m_ModelSkeleton.m_arrModelJoints[i].m_nJointCRC32Lower = g_pCrc32Gen->GetCRC32Lowercase(pJointName);

		pCGAModel->m_ModelSkeleton.m_poseData.m_pJointsAbsolute[i] =
			pCGAModel->m_ModelSkeleton.m_poseData.m_pJointsRelative[i];
		int32 p = pCGAModel->m_ModelSkeleton.m_arrModelJoints[i].m_idxParent;
		if (p >= 0)
		{
			pCGAModel->m_ModelSkeleton.m_poseData.m_pJointsAbsolute[i] =
				pCGAModel->m_ModelSkeleton.m_poseData.m_pJointsAbsolute[p] *
				pCGAModel->m_ModelSkeleton.m_poseData.m_pJointsAbsolute[i];
		}

//#ifdef _DEBUG





	}

	pCGAModel->m_ModelSkeleton.PrepareJointIDHash();
  
	//
	m_CtrlVec3.clear();
	m_CtrlQuat.clear();
	//
	return pCGAModel;
}



//////////////////////////////////////////////////////////////////////////
void CryCGALoader::LoadAnimations( const char *cgaFile, CCharacterModel* pCGAModel, uint32 unique_model_id, uint32 nLoadingFlags )
{
	LOADING_TIME_PROFILE_SECTION;

	// Load all filename_***.anm files.
	char filter[_MAX_PATH];
	char drive[_MAX_DRIVE];
	char dir[_MAX_DIR];
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];

	portable_splitpath( cgaFile,drive,dir,fname,ext );
	strcat( fname,"_*");
	portable_makepath( filter, drive,dir,fname,"anm" );

	char fullpath[_MAX_PATH];
	char filename[_MAX_PATH];
	portable_makepath( fullpath, drive,dir,NULL,NULL );

	ICryPak *pack = gEnv->pCryPak;

	// Search files that match filter specification.
	_finddata_t fd;
	int res;
	intptr_t handle;
	if ((handle = pack->FindFirst( filter,&fd )) != -1)
	if (handle != -1)
	{
		do
		{
			// ModelAnimationHeader file found, load it.
			strcpy( filename,fullpath );
			strcat( filename,fd.name );
			LoadAnimationANM( filename, pCGAModel,unique_model_id, nLoadingFlags );
			res = pack->FindNext( handle,&fd );

			SLICE_AND_SLEEP();
		} while (res >= 0);
		pack->FindClose(handle);
	}
}


//////////////////////////////////////////////////////////////////////////
bool CryCGALoader::LoadAnimationANM( const char* animFile, CCharacterModel* pCGAModel, uint32 unique_model_id, uint32 nLoadingFlags )
{
	// Get file name, this is a name of application.

//#ifdef XENON
//
////	return false;
//
//#endif

	assert( strlen(animFile)<_MAX_PATH );
	char fname[_MAX_PATH];
	strcpy( fname,animFile );
	CryStringUtils::StripFileExtension(fname);
	const char *sAnimName = CryStringUtils::FindFileNameInPath(fname);

	const char *sName = strchr(sAnimName,'_');
	if (sName)
		sName += 1;
	else
		sName = sAnimName;

//------------------------------------------------------------------------------

	CLoaderTCB loader;

	CHeaderTCB * pSkinningInfo		= loader.LoadTCB(animFile, 0) ;;//g_pI3DEngine->LoadChunkFileContent( animFile );
	if (pSkinningInfo==0)
	{
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING,	VALIDATOR_FLAG_FILE,animFile,	"Failed to load ANM-file: %s", animFile );
		return 0;
	}

	InitNodes(pSkinningInfo,pCGAModel,animFile,sName, 0, unique_model_id, nLoadingFlags );

	return true;
}


//////////////////////////////////////////////////////////////////////////
namespace CryCGALoaderHelper
{

static int GetLodIndex(const char* const pName)
{
	static const size_t lodNamePrefixLength = strlen(CGF_NODE_NAME_LOD_PREFIX);
	if (strnicmp(pName, CGF_NODE_NAME_LOD_PREFIX, lodNamePrefixLength))
	{
		return -1;
	}

	int value = 0;
	const char* p = pName + lodNamePrefixLength;
	while ((p[0] >= '0') && (p[0] <= '9'))
	{
		value = value * 10 + (p[0] - '0');
		++p;
	}

	return value;
}

} // namespace CryCGALoaderHelper


//////////////////////////////////////////////////////////////////////////
void CryCGALoader::InitNodes( CHeaderTCB* pSkinningInfo, CCharacterModel* pCGAModel, const char* animFile, const string& strAnimationName, bool bMakeNodes, uint32 unique_model_id, uint32 nLoadingFlags )
{

	//-------------------------------------------------------------------------
	//----------        copy animation timing-values            ---------------
	//-------------------------------------------------------------------------
	m_arrNodeAnims.clear();
	m_ticksPerFrame			=	TICKS_PER_FRAME;	//pSkinningInfo->m_nTicksPerFrame;
	m_secsPerTick				= SECONDS_PER_TICK;	//pSkinningInfo->m_secsPerTick;
	m_start							= pSkinningInfo->m_nStart;
	m_end								= pSkinningInfo->m_nEnd;
	assert(m_ticksPerFrame==TICKS_PER_FRAME);

	m_ModelAnimationHeader.SetAnimName(strAnimationName);//m_strAnimName = strAnimationName;

	//-------------------------------------------------------------------------
	//----------             copy animation tracks              ---------------
	//-------------------------------------------------------------------------
	m_CtrlVec3.clear();
	uint32 numTracksVec3 = pSkinningInfo->m_TrackVec3QQQ.size();
	m_CtrlVec3.reserve(numTracksVec3);
	for (uint32 t=0; t<numTracksVec3; t++)
	{
		CControllerTCBVec3 Track;
		uint32 nkeys = pSkinningInfo->m_TrackVec3QQQ[t]->size();
		Track.resize(nkeys);
		for (uint32 i=0; i<nkeys; i++)
		{
			Track.key(i).flags			= 0;

			f32 Qtime= (f32)pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).time;

			Track.key(i).time			= (f32)pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).time / TICKS_CONVERT;
			Track.key(i).value			= pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).val;
			Track.key(i).tens			= pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).t;
			Track.key(i).cont			= pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).c;
			Track.key(i).bias			= pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).b;
			Track.key(i).easefrom	= pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).eout;
			Track.key(i).easeto		= pSkinningInfo->m_TrackVec3QQQ[t]->operator[](i).ein;
		}

		if (pSkinningInfo->m_TrackVec3FlagsQQQ[t].f0)
			Track.ORT( spline::TCBSpline<Vec3>::ORT_CYCLE );
		else if (pSkinningInfo->m_TrackVec3FlagsQQQ[t].f1)
			Track.ORT( spline::TCBSpline<Vec3>::ORT_LOOP );
		Track.comp_deriv();// Precompute spline tangents.
		m_CtrlVec3.push_back(Track);
	}

	m_CtrlQuat.clear();
	uint32 numTracksQuat = pSkinningInfo->m_TrackQuat.size();

	m_CtrlQuat.reserve(numTracksQuat);
	for (uint32 t=0; t<numTracksQuat; t++)
	{
		spline::TCBAngleAxisSpline Track;
		uint32 nkeys = pSkinningInfo->m_TrackQuat[t]->size();
		Track.resize(nkeys);
		for (uint32 i=0; i<nkeys; i++)
		{
			Track.key(i).flags			= 0;
			Track.key(i).time			= (float)pSkinningInfo->m_TrackQuat[t]->operator[](i).time / TICKS_CONVERT;// * secsPerTick;
			Track.key(i).angle			= pSkinningInfo->m_TrackQuat[t]->operator[](i).val.w;	//TCBAngAxisSpline stores relative rotation angle-axis.
			Track.key(i).axis			= pSkinningInfo->m_TrackQuat[t]->operator[](i).val.v;	//@FIXME rotation direction somehow differ from Max.
			Track.key(i).tens			= pSkinningInfo->m_TrackQuat[t]->operator[](i).t;
			Track.key(i).cont			= pSkinningInfo->m_TrackQuat[t]->operator[](i).c;
			Track.key(i).bias			= pSkinningInfo->m_TrackQuat[t]->operator[](i).b;
			Track.key(i).easefrom	= pSkinningInfo->m_TrackQuat[t]->operator[](i).eout;
			Track.key(i).easeto		= pSkinningInfo->m_TrackQuat[t]->operator[](i).ein;
		}

		if (pSkinningInfo->m_TrackQuatFlagsQQQ[t].f0)
			Track.ORT( spline::TCBAngleAxisSpline::ORT_CYCLE );
		else if (pSkinningInfo->m_TrackQuatFlagsQQQ[t].f1)
			Track.ORT( spline::TCBAngleAxisSpline::ORT_LOOP );
		Track.comp_deriv();// Precompute spline tangents.
		m_CtrlQuat.push_back(Track);
	}

	m_arrControllers=pSkinningInfo->m_arrControllersTCB;
	
#ifdef _DEBUG
	uint32 numController = m_arrControllers.size();
#endif

	//-------------------------------------------------------------------------
	//-------------------------------------------------------------------------
	//-------------------------------------------------------------------------


	CContentCGF* pCGF = g_pI3DEngine->CreateChunkfileContent(animFile);
	bool bLoaded = g_pI3DEngine->LoadChunkFileContent( pCGF, animFile );
	if (!bLoaded)
	{
		//error
		return;
	}

	uint32 numChunks2 = pCGF->GetSkinningInfo()->m_numChunks;
	m_arrChunkNodes.resize(numChunks2*CONT_EXTENSION);
	for (uint32 i=0; i<numChunks2*CONT_EXTENSION; i++) 
		m_arrChunkNodes[i].active=0;

	IStatObj* pRootStaticObj = g_pI3DEngine->LoadStatObj( pCGAModel->m_strFilePath.c_str(), NULL, NULL, true, nLoadingFlags );

	pCGAModel->pCGA_Object = pRootStaticObj;

	uint32 numNodes2 = pCGF->GetNodeCount();
	assert(numNodes2);

	uint32 MeshNodeCounter=0;
	for(uint32 n=0; n<numNodes2; n++) 
	{
		if (pCGF->GetNode(n)->type==CNodeCGF::NODE_MESH)	
			MeshNodeCounter+=(pCGF->GetNode(n)!=0);
	}

	std::vector<QuatT> jointsRelative;
	if (uint32 jointCount = pCGAModel->m_ModelSkeleton.m_poseData.GetJointCount())
	{
		jointsRelative.resize(jointCount);
		for (uint32 i=0; i<jointCount; ++i)
			jointsRelative[i] = pCGAModel->m_ModelSkeleton.m_poseData.m_pJointsRelative[i];
	}

	CNodeCGF* pGFXNode2=0;
	//uint32 nodecounter=0;
	for(uint32 n=0; n<numNodes2; n++) 
	{
		uint32 MeshNode   = pCGF->GetNode(n)->type==CNodeCGF::NODE_MESH;	
		uint32 HelperNode = pCGF->GetNode(n)->type==CNodeCGF::NODE_HELPER;	
		if (MeshNode || HelperNode)	
		{	
			pGFXNode2 = pCGF->GetNode(n); 
			assert(pGFXNode2);

			// Try to create object.
//			IStatObj::SSubObject *pSubObject = NULL;

			NodeDesc nd;
			nd.active				= 1;
			nd.parentID			= pGFXNode2->nParentChunkId;
			nd.pos_cont_id	= pGFXNode2->pos_cont_id;
			nd.rot_cont_id	= pGFXNode2->rot_cont_id;
			nd.scl_cont_id	= pGFXNode2->scl_cont_id;

			int numChunks = (int)m_arrChunkNodes.size();

			if (nd.pos_cont_id!=0xffff)
				assert( nd.pos_cont_id < numChunks );
			if (nd.rot_cont_id!=0xffff)
				assert( nd.rot_cont_id < numChunks );
			if (nd.scl_cont_id!=0xffff)
				assert( nd.scl_cont_id < numChunks );

			assert(pGFXNode2->nChunkId<(int)numChunks);

			pCGAModel->m_ModelAABB=AABB( Vec3(-2,-2,-2),Vec3(+2,+2,+2) );

			if (bMakeNodes)
			{
				// FindSubObject will only cut the characters before " ".
				// If there are any space in a joint name, it will fail. Use FindSubObject_StrStr instead.
				IStatObj::SSubObject *pSubObj = pRootStaticObj->FindSubObject_CGA( pGFXNode2->name );

				if (pSubObj==0 && MeshNodeCounter!=1)
					continue;

				IStatObj* pStaticObj = 0;
				if (MeshNodeCounter==1 && MeshNode) 
					pStaticObj=pRootStaticObj;
				else if(pSubObj)
					pStaticObj = pSubObj->pStatObj;
				else
					continue;

				nd.node_idx				= pCGAModel->m_ModelSkeleton.m_arrModelJoints.size();

				uint16 ParentID = 0xffff;  
				if (pGFXNode2->nParentChunkId != 0xffffffff)
				{
					assert(pGFXNode2->nParentChunkId<(int)numChunks);
					uint32 numJoints = pCGAModel->m_ModelSkeleton.m_arrModelJoints.size();
					for (uint32 i=0; i<numJoints; i++)
						if (pGFXNode2->nParentChunkId==pCGAModel->m_ModelSkeleton.m_arrModelJoints[i].m_ObjectID)	
							ParentID=i;
				}

				CModelJoint mj;
				mj.m_nJointCRC32		= unique_model_id+nd.node_idx;
				mj.m_ObjectID			= pGFXNode2->nChunkId;  //NOTE:: this is a place-holder to store the chunk-id
				mj.m_idxParent			=	ParentID;
				mj.m_CGAObject			= pStaticObj;
				mj.SetJointName(pGFXNode2->name);
				mj.m_NodeID					=	nd.node_idx;
				mj.m_qDefaultRelPhysParent[0].SetIdentity();
				mj.m_qDefaultRelPhysParent[1].SetIdentity();
				jointsRelative.push_back(QuatT(!pGFXNode2->rot, pGFXNode2->pos));
				pCGAModel->m_ModelSkeleton.m_arrModelJoints.push_back(mj);

				m_arrChunkNodes[pGFXNode2->nChunkId] = nd;
		//		assert(nodecounter==n);
		//		nodecounter++;
			//	g_nControllerJID++;
			}
			else
			{
				uint32 numJoints = pCGAModel->m_ModelSkeleton.m_arrModelJoints.size();
				for (uint32 i=0; i<numJoints; i++)
				{
					if (stricmp(pCGAModel->m_ModelSkeleton.m_arrModelJoints[i].GetJointName(), pGFXNode2->name) == 0)
					{
						nd.node_idx = i;
						break;
					}
				}
				m_arrChunkNodes[pGFXNode2->nChunkId] = nd;
			}
		}
	}

	if (uint32 jointCount = uint32(jointsRelative.size()))
	{
		pCGAModel->m_ModelSkeleton.m_poseData.AllocateData(jointCount);
		for (uint32 i=0; i<jointCount; ++i)
			pCGAModel->m_ModelSkeleton.m_poseData.m_pJointsRelative[i] = jointsRelative[i];
	}

	//------------------------------------------------------------------------
	//---    init nodes                                                    ---
	//------------------------------------------------------------------------
	uint32 numControllers0 = m_CtrlVec3.size();
	uint32 numControllers1 = m_CtrlQuat.size();

	uint32 numAktiveNodes = 0;
	uint32 numNodes = m_arrChunkNodes.size();
	for (uint32 i=0; i<numNodes; i++)
		numAktiveNodes+=m_arrChunkNodes[i].active;

	m_arrNodeAnims.clear();
	m_arrNodeAnims.resize(numAktiveNodes);

	if (numAktiveNodes<m_DefaultNodeCount)
		numAktiveNodes=m_DefaultNodeCount;

	m_arrNodeAnims.resize(numAktiveNodes);


	for (uint32 i=0; i<numNodes; i++)
	{
		NodeDesc nd = m_arrChunkNodes[i];

		if (nd.active==0) 
			continue;

		if (nd.node_idx==0xffff)
			continue;

		uint32 numAnims = m_arrNodeAnims.size();

		uint32 id = pCGAModel->m_ModelSkeleton.m_arrModelJoints[nd.node_idx].m_NodeID;
		if ( id<0 || id>=numAnims)
			continue;


		// find controllers.
		if (nd.pos_cont_id!=0xffff)
		{
			m_arrNodeAnims[id].m_active.p=0;
			CControllerType pTCB = m_arrControllers[nd.pos_cont_id];
			if (pTCB.m_controllertype==POSCHANNEL)
			{
				m_arrNodeAnims[id].m_active.p=1;
				m_arrNodeAnims[id].m_posTrack = m_CtrlVec3[pTCB.m_index];
			}
		}

		if (nd.rot_cont_id!=0xffff)
		{
			m_arrNodeAnims[id].m_active.o=0;
			CControllerType pTCB = m_arrControllers[nd.rot_cont_id];
			if (pTCB.m_controllertype==ROTCHANNEL)
			{
				m_arrNodeAnims[id].m_active.o=1;
				m_arrNodeAnims[id].m_rotTrack = m_CtrlQuat[pTCB.m_index];
			}
		}

		if (nd.scl_cont_id!=0xffff)
		{
			m_arrNodeAnims[id].m_active.s=0;
			CControllerType pTCB = m_arrControllers[nd.scl_cont_id];
			if (pTCB.m_controllertype==POSCHANNEL)
			{
				m_arrNodeAnims[id].m_active.s=1;
				m_arrNodeAnims[id].m_sclTrack = m_CtrlVec3[pTCB.m_index];
			}
		}
	}

//-------------------------------------------------------------------------

	if (!m_CtrlVec3.empty() || !m_CtrlQuat.empty() )
		LoadANM( pCGAModel, animFile, strAnimationName, m_arrNodeAnims, unique_model_id );

	uint32 numJoints2 = pCGAModel->m_ModelSkeleton.m_arrModelJoints.size();
	for (uint32 a=0; a<numJoints2; a++)
		pCGAModel->m_ModelSkeleton.m_arrModelJoints[a].m_numChildren=0;
	for (uint32 a=0; a<numJoints2; a++)
	{
		for (uint32 b=0; b<numJoints2; b++)
		{
			if (a==pCGAModel->m_ModelSkeleton.m_arrModelJoints[b].m_idxParent)
				pCGAModel->m_ModelSkeleton.m_arrModelJoints[a].m_numChildren++;
		}	
	}

	// create merged skinned mesh with one weight per vertex
	// handle lods
	if( Console::GetInst().ca_DrawCGAAsSkin==1 )
	{
		int numLods = 1;
		int numVerticies = 0;
		bool bCanBeMerged = true;
		//  merge nodes to skin render mesh
		// sort by materials groups
		std::set<int> mtlSubsets;
		for(uint32 n=0; n<numNodes2; n++)
		{
			CNodeCGF* pGFXNode= pCGF->GetNode(n);
			assert(pGFXNode);
			if( !pGFXNode )
				continue;
			//
			uint32 MeshNode   = pGFXNode->type==CNodeCGF::NODE_MESH;
			uint32 HelperNode = pGFXNode->type==CNodeCGF::NODE_HELPER;
			if (HelperNode && (pGFXNode->pParent != NULL))
			{
				const int lod = CryCGALoaderHelper::GetLodIndex(pGFXNode->name);
				if ((lod < g_nMaxGeomLodLevels) && (lod >= numLods))
				{
					numLods = lod + 1;
				}
			}
			if (!MeshNode)
				continue;
			//
			CMesh* pMeshPart = pGFXNode->pMesh;
			if( pMeshPart )
			{
				numVerticies+=pMeshPart->GetVertexCount();
				//
				for(int s=0;s<pMeshPart->m_subsets.size();s++)
				{
					mtlSubsets.insert(pMeshPart->m_subsets[s].nMatID);
					//          if( pMeshPart->m_subsets[s].nFirstVertId==999999 || pMeshPart->m_subsets[s].nNumVerts<0 )
					//          {
					//            bCanBeMerged = false;
					//            break;
					//          }
				}
			}
		}
		if( numVerticies>=(1 << 16) )
		{
			bCanBeMerged = false;
		}
		//
		if( !bCanBeMerged )
		{
			CryLog("Cannot be merged to one skin instance");
		}
		else
		{
			//
			pCGAModel->m_arrModelMeshes.resize(numLods);
			//
			for(int nLod = 0;nLod<numLods;nLod++)
			{
				pCGAModel->m_pRenderMeshs[nLod] = g_pIRenderer->CreateRenderMesh("CharacterCGA",pCGAModel->GetModelFilePath());
				// CreateRenderMesh create an object with an refcount of 1, by using a smart-ptr the refcount is incrased to 2, so use Release to correct the refcount
				pCGAModel->m_pRenderMeshs[nLod]->Release();
				//
				CMesh* pTgtMesh = new CMesh();
				pTgtMesh->SetVertexCount(0);
				pTgtMesh->SetIndexCount(0);
				pTgtMesh->SetTexCoordsAndTangentsCount(0);
				pTgtMesh->ReallocStream(CMesh::BONEMAPPING,0);
				pTgtMesh->m_bbox.Reset();
				pTgtMesh->m_subsets.resize(0);
				//
		//    std::vector<Vec3> arrBoneSpace;
				//
				std::set<int>::iterator it = mtlSubsets.begin();
				for(;it!=mtlSubsets.end();++it)
				{
					int mtlId = *(it);
					// create new subset if required
					if( pTgtMesh->m_subsets.size()==0 || pTgtMesh->m_subsets.back().nMatID!=mtlId )
					{
						pTgtMesh->m_subsets.push_back(SMeshSubset());
						pTgtMesh->m_subsets.back().nMatID=mtlId;
						pTgtMesh->m_subsets.back().nFirstVertId = pTgtMesh->GetVertexCount();
						pTgtMesh->m_subsets.back().nFirstIndexId = pTgtMesh->GetIndexCount();
					}
					uint32 numJoints = pCGAModel->m_ModelSkeleton.m_arrModelJoints.size();
					for (uint32 a=0; a<numJoints; a++)
					{
						stack_string jointname =  pCGAModel->m_ModelSkeleton.m_arrModelJoints[a].GetJointName();
						for(uint32 n=0; n<numNodes2; n++)
						{
							CNodeCGF* pGFXNode = pCGF->GetNode(n);
							assert(pGFXNode);
							if( !pGFXNode )
								continue;
							if( pGFXNode->name!=jointname )
								continue;
							//
							uint32 MeshNode = pGFXNode->type==CNodeCGF::NODE_MESH;
							if (!MeshNode)
								continue;
							//
							Matrix34 mtOffset; mtOffset.SetIdentity();
							//
							CMesh* pMeshPart = NULL;
							pMeshPart = pGFXNode->pMesh;
							assert(pMeshPart);
							if( nLod!=0 )
							{
								for(uint32 n1=0; n1<numNodes2; n1++)
								{
									CNodeCGF* pGFXNodeLod = pCGF->GetNode(n1);
									assert(pGFXNodeLod);
									if( !pGFXNodeLod )
										continue;
									// find
									if( pGFXNodeLod->type==CNodeCGF::NODE_HELPER )
									{
										if (CryCGALoaderHelper::GetLodIndex(pGFXNodeLod->name) == nLod)
										{
											// check is it lod of correct node
											if( pGFXNodeLod->pParent==pGFXNode )
											{
												if( pGFXNodeLod->pMesh )
												{
													pMeshPart = pGFXNodeLod->pMesh;
													mtOffset = pGFXNodeLod->localTM;
												}
												break;
											}
										}
									}
								}
							}
							if( !pMeshPart )
								continue;
							//
							for(int s=0;s<pMeshPart->m_subsets.size();s++)
							{
								const SMeshSubset& srcSubset = pMeshPart->m_subsets[s];
								if( srcSubset.nMatID!=mtlId )
									continue;
								//
								if( srcSubset.nNumVerts<0 )
									continue;
								//
								int startVertexToMerge = srcSubset.nFirstVertId;
								int numVerticesToMerge = srcSubset.nNumVerts;
								int startIndexToMerge = srcSubset.nFirstIndexId;
								int numIndiciesToMerge = srcSubset.nNumIndices;
								// SOMETIMES numVerts is incorrect
								// find good min and max AGAIN
								{
									int maxVertexInUse = 0;
									for(int n2=0;n2<numIndiciesToMerge;n2++)
									{
										int i = (int)pMeshPart->m_pIndices[n2+startIndexToMerge];
										startVertexToMerge = min(i,startVertexToMerge);
										maxVertexInUse = max(i,maxVertexInUse);
									}
									numVerticesToMerge = maxVertexInUse-startVertexToMerge+1;
								}
								// find bone id
								int boneid = -1;
								for (uint32 a2=0; a2<numJoints; a2++)
								{
									if( pGFXNode->name==pCGAModel->m_ModelSkeleton.m_arrModelJoints[a2].GetJointName() )
									{
										boneid = a2;
										break;
										//pCGAModel->m_ModelSkeleton.m_arrModelJoints[a].
									}
								}
								//
								if( boneid==-1 )
								{
									CryLog("Should never happen");
								}
								//
								if( boneid==-1 )
									continue;
								//
								int numTgtVerticesBefore = pTgtMesh->GetVertexCount();
								int numTgtIndiciesBefore = pTgtMesh->GetIndexCount();
								//
								std::vector<int> ignoreStreams;
								ignoreStreams.push_back(CMesh::COLORS_0);
								ignoreStreams.push_back(CMesh::COLORS_1);
								//
								pTgtMesh->Append(*pMeshPart,startVertexToMerge,numVerticesToMerge,startIndexToMerge/3,numIndiciesToMerge/3,&ignoreStreams);
						//    arrBoneSpace.resize(arrBoneSpace.size()+numVerticesToMerge);
								//
								PodArray<uint16>& TgtArrGlobalBonesPerSubset = pTgtMesh->m_subsets.back().m_arrGlobalBonesPerSubset;
								int boneIndex = TgtArrGlobalBonesPerSubset.Find(boneid);
								if( boneIndex==-1 )
								{
									if( TgtArrGlobalBonesPerSubset.size()>=NUM_MAX_BONES_PER_GROUP )
									{
										// new subset
										pTgtMesh->m_subsets.push_back(SMeshSubset());
										pTgtMesh->m_subsets.back().nMatID=mtlId;
										pTgtMesh->m_subsets.back().nFirstVertId = numTgtVerticesBefore;
										pTgtMesh->m_subsets.back().nFirstIndexId = numTgtIndiciesBefore;
									}
									pTgtMesh->m_subsets.back().m_arrGlobalBonesPerSubset.push_back(boneid);
									boneIndex = pTgtMesh->m_subsets.back().m_arrGlobalBonesPerSubset.size()-1;
								}
								/*
								SMeshSubset& backSubset = pTgtMesh->m_subsets.back();
								if( boneIndex>=backSubset.m_arrGlobalBonesPerSubset.size())
								{
								int y=0;
								}
								*/
								// add skin stream
								if( !pMeshPart->m_pBoneMapping )
								{
									pTgtMesh->ReallocStream( CMesh::BONEMAPPING,numTgtVerticesBefore+numVerticesToMerge );
								}
								// update bone indicies
								void* pBuf;
								int nBoneMappingElemSize;
								pTgtMesh->GetStreamInfo(CMesh::BONEMAPPING,pBuf,nBoneMappingElemSize);
								SMeshBoneMapping* pBoneMapping = (SMeshBoneMapping*)pBuf;
								// check for good skin group
								for(int v=numTgtVerticesBefore;v<numTgtVerticesBefore+numVerticesToMerge;v++)
								{
									// for lod > 0
									pTgtMesh->m_pPositions[v] = mtOffset.TransformPoint(pTgtMesh->m_pPositions[v]);
									//
									pBoneMapping[v].boneIDs[0] = boneIndex;
									pBoneMapping[v].boneIDs[1] = 0;
									pBoneMapping[v].boneIDs[2] = 0;
									pBoneMapping[v].boneIDs[3] = 0;
									//
									pBoneMapping[v].weights[0] = 255;
									pBoneMapping[v].weights[1] = 0;
									pBoneMapping[v].weights[2] = 0;
									pBoneMapping[v].weights[3] = 0;
									//
									//arrBoneSpace[v] = pCGAModel->m_ModelSkeleton.m_arrModelJoints[boneid].m_DefaultAbsPose.t;
								}
								//
								//
								pTgtMesh->m_subsets.back().nNumVerts+=numVerticesToMerge;
								pTgtMesh->m_subsets.back().nNumIndices+=numIndiciesToMerge;
							}
						}
					}
				}
				//
				pTgtMesh->ReallocStream(CMesh::SHAPEDEFORMATION,pTgtMesh->GetVertexCount());
				for(int n=0;n<pTgtMesh->GetVertexCount();n++)
				{
					pTgtMesh->m_pShapeDeformation[n].thin = pTgtMesh->m_pPositions[n];
					pTgtMesh->m_pShapeDeformation[n].fat = pTgtMesh->m_pPositions[n];
					pTgtMesh->m_pShapeDeformation[n].index.set(0,0,0,0);
				}
				// regen subset radius and center
				for(int s=0;s<pTgtMesh->m_subsets.size();s++)
				{
					SMeshSubset& subset = pTgtMesh->m_subsets[s];
					AABB bound(AABB::RESET);
					for(int v=subset.nFirstVertId;v<subset.nFirstVertId+subset.nNumVerts;v++)
					{
						bound.Add(pTgtMesh->m_pPositions[v]);
					}
					subset.vCenter = bound.GetCenter();
					subset.fRadius = bound.GetRadius();
				}
				pTgtMesh->RecomputeTexMappingDensity();
				//
				pCGAModel->m_pRenderMeshs[nLod]->SetMesh( *pTgtMesh, 0, FSM_MORPH_TARGETS | FSM_CREATE_DEVICE_MESH );
				//
				pCGAModel->m_arrModelMeshes[nLod].m_nLOD = nLod;
				pCGAModel->m_arrModelMeshes[nLod].m_pModel = pCGAModel;
			}
		}
	}
	g_pI3DEngine->ReleaseChunkfileContent(pCGF);
}

// loads the animations from the array: pre-allocates the necessary controller arrays
// the 0th animation is the default animation
uint32 CryCGALoader::LoadANM ( CCharacterModel* pModel,const char* pFilePath, const char* pAnimName, DynArray<CControllerTCB>& m_LoadCurrAnimation, uint32 unique_model_id  )
{
	uint32 nAnimID = 0;
		pModel->m_AnimationSet.prepareLoadANMs ( 1 );
	int32 rel = pModel->m_AnimationSet.LoadFileANM( pFilePath, pAnimName, m_LoadCurrAnimation, this, unique_model_id  );
	if (rel >= 0)
		nAnimID++;
	else
		AnimFileWarning(pModel->GetModelFilePath(),"Animation could not be read" );

	return nAnimID;
}

//////////////////////////////////////////////////////////////////////////
void CryCGALoader::Reset()
{
	m_CtrlVec3.resize(0);
	m_CtrlQuat.resize(0);

	m_arrControllers.resize(0);
	m_arrChunkNodes.resize(0);

	m_arrNodeAnims.resize(0);
}
